package org.gpp.proj1.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.util.CloneImportExport;
import com.jme.util.LoggingSystem;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.model.XMLparser.JmeBinaryReader;
import com.jmex.model.XMLparser.XMLtoBinary;
import com.jmex.model.XMLparser.Converters.AseToJme;
import com.jmex.model.XMLparser.Converters.FormatConverter;
import com.jmex.model.XMLparser.Converters.MaxToJme;
import com.jmex.model.XMLparser.Converters.Md2ToJme;
import com.jmex.model.XMLparser.Converters.Md3ToJme;
import com.jmex.model.XMLparser.Converters.MilkToJme;
import com.jmex.model.XMLparser.Converters.ObjToJme;

public class ModelFactory {

	/**
	 * The factory's unique instance
	 */
	private static ModelFactory instance = null;

	/**
	 * The model map
	 */
	private Map<String, Node> modelMap;

	/**
	 * Default, private constructor
	 */
	private ModelFactory() {
		modelMap = new HashMap<String, Node>();
	}

	/**
	 * @return The model builder's unique instance
	 */
	public static ModelFactory getInstance() {
		if( instance == null ) {
			instance = new ModelFactory();
		}

		return instance;
	}

	/**
	 * Removes all the models from the model map
	 */
	public void resetModels() {
		modelMap.clear();
	}

	/**
	 * @param modelFile
	 * @return The model for the given file
	 */
	public Spatial getModel( String modelFile ) {
		if( ! modelMap.containsKey( modelFile ) ) {
			loadModel( modelFile );
		}

		// If the file doesn't exist, the model is null
		if( modelMap.get( modelFile ) == null ) {
			return null;
		}

		return cloneNode( modelMap.get( modelFile ) );
	}

	protected static Node cloneNode(Node m) {
		CloneImportExport cloner = new CloneImportExport();
		cloner.saveClone( m );
		return (Node) cloner.loadClone();
	}

	/**
	 * Loads an model from a file and puts it on de model map
	 * 
	 * @param modelFile
	 */
	private void loadModel( String modelFile ) {
		InputStream in = null;
		
		BinaryImporter importer = BinaryImporter.getInstance();
		try {
			ByteArrayOutputStream BO = new ByteArrayOutputStream();

			File f = new File(modelFile);
			in = new BufferedInputStream( new FileInputStream( f ) );

			if( f.getName().toLowerCase().endsWith(".xml") ) {
				// We have to keep this deprecated code becase JME 0.11
				// XML to binary conversion DOES NOT work. Yet.
				XMLtoBinary converter = new XMLtoBinary();				
				JmeBinaryReader jbr = new JmeBinaryReader();
				jbr.setProperty("bound", "box");
				Node modelNode = null;
				try {
				    converter.sendXMLtoBinary(in, BO);
				    modelNode = jbr.loadBinaryFormat(new ByteArrayInputStream(BO.toByteArray()));
				} catch (IOException ex) {
				    ex.printStackTrace();
				}
				
				modelMap.put( modelFile, modelNode );
			} else {
				FormatConverter converter = null;
	
				if (f.getName().toLowerCase().endsWith(".ms3d")) {
					converter = new MilkToJme();
				} else if (f.getName().toLowerCase().endsWith(".3ds")) {
					converter = new MaxToJme();
				} else if (f.getName().toLowerCase().endsWith(".ase")) {
					converter = new AseToJme();
				} else if (f.getName().toLowerCase().endsWith(".md2")) {
					converter = new Md2ToJme();
				} else if (f.getName().toLowerCase().endsWith(".md3")) {
					converter = new Md3ToJme();
				} else if (f.getName().toLowerCase().endsWith(".obj")) {
					converter = new ObjToJme();
				} else {
					LoggingSystem.getLogger().warning( "Can't load model file format: " + modelFile );
				}
	
				converter.convert( in, BO );
				
				Node model = (Node) importer.load( BO.toByteArray() );
				
				modelMap.put( modelFile, model );
			}

		} catch (IOException e) {
			LoggingSystem.getLogger().warning( "Can't load model file: " + modelFile );
			LoggingSystem.getLogger().throwing( this.getClass().getName(), "loadModel", e );
			return;
		} catch (Exception e) {
			LoggingSystem.getLogger().warning( "Can't load model file: " + modelFile );
			LoggingSystem.getLogger().throwing( this.getClass().getName(), "loadModel", e );
		} finally {
			if( in != null ) {
				try {
					in.close();
				} catch (IOException e) {
					LoggingSystem.getLogger().warning( "Can't close model file: " + modelFile );
					LoggingSystem.getLogger().throwing( this.getClass().getName(), "loadModel", e );
				}
			}
		}
	}
}
